/*
 * Created Jul 8, 2008
 *
 * Copyright ThinkTank Maths Limited 2008
 *
 * This file is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This file is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this file. If not, see <http://www.gnu.org/licenses/>.
 */
package com.google.code.smon.util;

import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;

/**
 * A {@link Formatter} that may be customised in a {@code logging.properties}
 * file. The syntax of the property
 * {@code com.thinktankmaths.logging.TerseFormatter.format}
 * specifies the output. A newline will be appended to the string and the
 * following special characters will be expanded (case sensitive):-
 * <ul>
 * <li>{@code %m} - message</li>
 * <li>{@code %L} - log level</li>
 * <li>{@code %n} - name of the logger</li>
 * <li>{@code %t} - a timestamp (in ISO-8601 "yyyy-MM-dd HH:mm:ss Z" format)</li>
 * <li>{@code %M} - source method name (if available, otherwise "?")</li>
 * <li>{@code %c} - source class name (if available, otherwise "?")</li>
 * <li>{@code %C} - source simple class name (if available, otherwise "?")</li>
 * <li>{@code %T} - thread ID</li>
 * </ul>
 * The default format is {@value #DEFAULT_OUTFORMAT}. Curly brace characters are not
 * allowed.
 *
 * @author Samuel Halliday
 */
public class CustomFormatter extends Formatter {

    private static final String DEFAULT_OUTFORMAT = "%L: %m [%c.%M %t]";
    private static final String DEFAULT_DATEFORMAT = "yyyy-MM-dd HH:mm:ss Z";
    private final MessageFormat messageFormat;
    private final DateFormat dateFormat;

    /** */
    public CustomFormatter() {
        this(null);
    }

    public CustomFormatter(String format) {
        this(format, null);
    }

    public CustomFormatter(String format, String dateFormat) {
        super();// convert it into the MessageFormat form
        if (format == null) {
            String configFormat = LogManager.getLogManager().getProperty(getClass().
                    getName() + ".format");
            if (configFormat == null || configFormat.trim().length() == 0) {
                configFormat = DEFAULT_OUTFORMAT;
            }
            if (configFormat.contains("{") || configFormat.contains("}")) {
                throw new IllegalArgumentException("curly braces not allowed");
            }
            format = configFormat;
        }
        if (dateFormat == null) {
            String configDateFormat = LogManager.getLogManager().getProperty(getClass().
                    getName() + ".dateFormat");
            if (configDateFormat == null || configDateFormat.trim().length() == 0) {
                configDateFormat = DEFAULT_DATEFORMAT;
            }
            dateFormat = configDateFormat;
        }

        format = format.replace("%L", "{0}").replace("%m", "{1}").replace("%M",
                "{2}").replace("%t", "{3}").replace("%c", "{4}").replace("%T", "{5}").
                replace("%n", "{6}").replace("%C", "{7}") + "\n";

        this.messageFormat = new MessageFormat(format);
        this.dateFormat = new SimpleDateFormat(dateFormat);
    }

    @Override
    public String format(LogRecord record) {
        String[] arguments = new String[8];
        // %L
        arguments[0] = record.getLevel().toString();
        arguments[1] = record.getMessage();
        // sometimes the message is empty, but there is a throwable
        if (arguments[1] == null || arguments[1].length() == 0) {
            Throwable thrown = record.getThrown();
            if (thrown != null) {
                arguments[1] = thrown.getMessage();
            }
        }
        // %m
        arguments[1] = record.getMessage();
        // %M
        if (record.getSourceMethodName() != null) {
            arguments[2] = record.getSourceMethodName();
        } else {
            arguments[2] = "?";
        }
        // %t
        Date date = new Date(record.getMillis());
        synchronized (dateFormat) {
            arguments[3] = dateFormat.format(date);
        }
        // %c
        if (record.getSourceClassName() != null) {
            arguments[4] = record.getSourceClassName();
        } else {
            arguments[4] = "?";
        }
        // %T
        arguments[5] = Integer.valueOf(record.getThreadID()).toString();
        // %n
        arguments[6] = record.getLoggerName();
        // %C
        int start = arguments[4].lastIndexOf(".") + 1;
        if (start > 0 && start < arguments[4].length()) {
            arguments[7] = arguments[4].substring(start);
        } else {
            arguments[7] = arguments[4];
        }


        synchronized (messageFormat) {
            String message = messageFormat.format(arguments);
            if (record.getThrown() != null) {
                message += record.getThrown().getClass().getSimpleName() + ": " + record.getThrown().getMessage() + "\n";
            }
            if (record.getParameters() != null) {
                int cnt = 1;
                for (Object object : record.getParameters()) {
                    message += "\tparm" + cnt + ": " + object + "\n";
                    ++cnt;
                }

            }
            return message;
        }
    }
}
